const toString = Object.prototype.toString

export const toTypeString = val => toString.call(val)

export const isString = obj => toTypeString(obj) === '[object String]'

/**
 * 1毫米转对应的像素点
 * @param dpi
 * @return {number}
 */
const millimeterToDot = (dpi = 300) => Math.round(dpi / 25.4)

const dots = millimeterToDot()

const labelWidth = 75

const labelHeight = 50

const gap = 10

const mXAxisOffset = 6

/**
 * 毫米转像素
 * @param millimeter
 * @return {number}
 */
const millimeterToPixel = millimeter => Math.round(dots * millimeter)

/**
 * 字符串分行
 * @param val 字符串
 * @param size 每行字数,汉字占两个字符
 * @return {*[]}
 */
export const splitString = (val, size = 10) => {
  const str = String(val)
  const lines = []
  let count = 0
  let words = ''
  for (let i = 0, length = str.length; i < length; i++) {
    const c = str.charCodeAt(i)
    if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
      // 非中文
      count += 1
    } else {
      count += 2
    }
    words += str.charAt(i)
    if (count >= size) {
      lines.push(words)
      words = ''
      count = 0
    }
  }
  if (words) {
    lines.push(words)
  }
  return lines
}

/* function floatAdd(arg1, arg2) {
  let r1, r2
  try { r1 = arg1.toString().split('.')[1].length } catch (e) { r1 = 0 }
  try { r2 = arg2.toString().split('.')[1].length } catch (e) { r2 = 0 }
  const m = Math.pow(10, Math.max(r1, r2))
  return (arg1 * m + arg2 * m) / m
} */

// 减
function floatSub(arg1, arg2) {
  let r1, r2
  try {
    r1 = arg1.toString().split('.')[1].length
  } catch (e) {
    r1 = 0
  }
  try {
    r2 = arg2.toString().split('.')[1].length
  } catch (e) {
    r2 = 0
  }
  const m = Math.pow(10, Math.max(r1, r2))
  // 动态控制精度长度
  const n = r1 >= r2 ? r1 : r2
  return Number(((arg1 * m - arg2 * m) / m).toFixed(n))
}

// 乘
/* function floatMul(arg1, arg2) {
  let m = 0; const s1 = arg1.toString(); const s2 = arg2.toString()
  try { m += s1.split('.')[1].length } catch (e) {}
  try { m += s2.split('.')[1].length } catch (e) {}
  return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
} */

// 除
/* function floatDiv(arg1, arg2) {
  let t1 = 0; let t2 = 0
  try { t1 = arg1.toString().split('.')[1].length } catch (e) {}
  try { t2 = arg2.toString().split('.')[1].length } catch (e) {}

  const r1 = Number(arg1.toString().replace('.', ''))

  const r2 = Number(arg2.toString().replace('.', ''))
  return (r1 / r2) * Math.pow(10, t2 - t1)
} */

class Rect {
  constructor(left, top, right, bottom, thickness = 0.3) {
    this.left = left
    this.top = top
    this.right = right
    this.bottom = bottom
    this.thickness = thickness
  }

  /**
   * Returns true if the rectangle is empty (left >= right or top >= bottom)
   */
  isEmpty() {
    return this.left >= this.right || this.top >= this.bottom
  }

  /**
   * @return true if the rectangle is valid (left <= right and top <= bottom).
   * @hide
   */
  isValid() {
    return this.left <= this.right && this.top <= this.bottom
  }

  /**
   * @return the rectangle's width. This does not check for a valid rectangle
   * (i.e. left <= right) so the result may be negative.
   */
  width() {
    return floatSub(this.right, this.left)
  }

  /**
   * @return the rectangle's height. This does not check for a valid rectangle
   * (i.e. top <= bottom) so the result may be negative.
   */
  height() {
    return floatSub(this.bottom, this.top)
  }

  /**
   * @return the horizontal center of the rectangle. If the computed value
   *         is fractional, this method returns the largest integer that is
   *         less than the computed value.
   */
  centerX() {
    return (this.left + this.right) >> 1
  }

  /**
   * @return the vertical center of the rectangle. If the computed value
   *         is fractional, this method returns the largest integer that is
   *         less than the computed value.
   */
  centerY() {
    return (this.top + this.bottom) >> 1
  }

  /**
   * @return the exact horizontal center of the rectangle as a float.
   */
  exactCenterX() {
    return (this.left + this.right) * 0.5
  }

  /**
   * @return the exact vertical center of the rectangle as a float.
   */
  exactCenterY() {
    return (this.top + this.bottom) * 0.5
  }

  /**
   * Set the rectangle to (0,0,0,0)
   */
  setEmpty() {
    this.left = this.right = this.top = this.bottom = 0
  }

  /**
   * Set the rectangle's coordinates to the specified values. Note: no range
   * checking is performed, so it is up to the caller to ensure that
   * left <= right and top <= bottom.
   *
   * @param left   The X coordinate of the left side of the rectangle
   * @param top    The Y coordinate of the top of the rectangle
   * @param right  The X coordinate of the right side of the rectangle
   * @param bottom The Y coordinate of the bottom of the rectangle
   */
  set(left, top, right, bottom) {
    this.left = left
    this.top = top
    this.right = right
    this.bottom = bottom
  }

  /**
   * Offset the rectangle by adding dx to its left and right coordinates, and
   * adding dy to its top and bottom coordinates.
   *
   * @param dx The amount to add to the rectangle's left and right coordinates
   * @param dy The amount to add to the rectangle's top and bottom coordinates
   */
  offset(dx, dy) {
    this.left += dx
    this.top += dy
    this.right += dx
    this.bottom += dy
  }

  /**
   * Offset the rectangle to a specific (left, top) position,
   * keeping its width and height the same.
   *
   * @param newLeft   The new "left" coordinate for the rectangle
   * @param newTop    The new "top" coordinate for the rectangle
   */
  offsetTo(newLeft, newTop) {
    this.right += newLeft - this.left
    this.bottom += newTop - this.top
    this.left = newLeft
    this.top = newTop
  }

  /**
   * Inset the rectangle by (dx,dy). If dx is positive, then the sides are
   * moved inwards, making the rectangle narrower. If dx is negative, then the
   * sides are moved outwards, making the rectangle wider. The same holds true
   * for dy and the top and bottom.
   *
   * @param dx The amount to add(subtract) from the rectangle's left(right)
   * @param dy The amount to add(subtract) from the rectangle's top(bottom)
   */
  inset(dx, dy) {
    this.left += dx
    this.top += dy
    this.right -= dx
    this.bottom -= dy
  }

  /**
   * Returns true if (x,y) is inside the rectangle. The left and top are
   * considered to be inside, while the right and bottom are not. This means
   * that for a x,y to be contained: left <= x < right and top <= y < bottom.
   * An empty rectangle never contains any point.
   *
   * @param x The X coordinate of the point being tested for containment
   * @param y The Y coordinate of the point being tested for containment
   * @return true iff (x,y) are contained by the rectangle, where containment
   *              means left <= x < right and top <= y < bottom
   */
  contains(x, y) {
    return (
      this.left < this.right &&
      this.top < this.bottom && // check for empty first
      x >= this.left &&
      x < this.right &&
      y >= this.top &&
      y < this.bottom
    )
  }

  /**
   * Returns true iff the 4 specified sides of a rectangle are inside or equal
   * to this rectangle. i.e. is this rectangle a superset of the specified
   * rectangle. An empty rectangle never contains another rectangle.
   *
   * @param left The left side of the rectangle being tested for containment
   * @param top The top of the rectangle being tested for containment
   * @param right The right side of the rectangle being tested for containment
   * @param bottom The bottom of the rectangle being tested for containment
   * @return true if the 4 specified sides of a rectangle are inside or
   *              equal to this rectangle
   */
  containsRect(left, top, right, bottom) {
    // check for empty first
    return (
      this.left < this.right &&
      this.top < this.bottom &&
      // now check for containment
      this.left <= left &&
      this.top <= top &&
      this.right >= right &&
      this.bottom >= bottom
    )
  }

  /**
   * If the rectangle specified by left,top,right,bottom intersects this
   * rectangle, return true and set this rectangle to that intersection,
   * otherwise return false and do not change this rectangle. No check is
   * performed to see if either rectangle is empty. Note: To just test for
   * intersection, use {@link #intersects(Rect, Rect)}.
   *
   * @param left The left side of the rectangle being intersected with this
   *             rectangle
   * @param top The top of the rectangle being intersected with this rectangle
   * @param right The right side of the rectangle being intersected with this
   *              rectangle.
   * @param bottom The bottom of the rectangle being intersected with this
   *             rectangle.
   * @return true if the specified rectangle and this rectangle intersect
   *              (and this rectangle is then set to that intersection) else
   *              return false and do not change this rectangle.
   */
  intersect(left, top, right, bottom) {
    if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
      if (this.left < left) this.left = left
      if (this.top < top) this.top = top
      if (this.right > right) this.right = right
      if (this.bottom > bottom) this.bottom = bottom
      return true
    }
    return false
  }

  /**
   * Returns true if this rectangle intersects the specified rectangle.
   * In no event is this rectangle modified. No check is performed to see
   * if either rectangle is empty. To record the intersection, use intersect()
   * or setIntersect().
   *
   * @param left The left side of the rectangle being tested for intersection
   * @param top The top of the rectangle being tested for intersection
   * @param right The right side of the rectangle being tested for
   *              intersection
   * @param bottom The bottom of the rectangle being tested for intersection
   * @return true iff the specified rectangle intersects this rectangle. In
   *              no event is this rectangle modified.
   */
  intersects(left, top, right, bottom) {
    return this.left < right && left < this.right && this.top < bottom && top < this.bottom
  }

  /**
   * Update this Rect to enclose itself and the specified rectangle. If the
   * specified rectangle is empty, nothing is done. If this rectangle is empty
   * it is set to the specified rectangle.
   *
   * @param left The left edge being unioned with this rectangle
   * @param top The top edge being unioned with this rectangle
   * @param right The right edge being unioned with this rectangle
   * @param bottom The bottom edge being unioned with this rectangle
   */
  union(left, top, right, bottom) {
    if (left < right && top < bottom) {
      if (this.left < this.right && this.top < this.bottom) {
        if (this.left > left) this.left = left
        if (this.top > top) this.top = top
        if (this.right < right) this.right = right
        if (this.bottom < bottom) this.bottom = bottom
      } else {
        this.left = left
        this.top = top
        this.right = right
        this.bottom = bottom
      }
    }
  }

  /**
   * Update this Rect to enclose itself and the [x,y] coordinate. There is no
   * check to see that this rectangle is non-empty.
   *
   * @param x The x coordinate of the point to add to the rectangle
   * @param y The y coordinate of the point to add to the rectangle
   */
  unionXY(x, y) {
    if (x < this.left) {
      this.left = x
    } else if (x > this.right) {
      this.right = x
    }
    if (y < this.top) {
      this.top = y
    } else if (y > this.bottom) {
      this.bottom = y
    }
  }
}

/**
 * 计算字符串宽度
 * @param text
 * @param fontSize
 * @return {number}
 */
export const measureText = (text, fontSize = 1) => {
  let length = 0
  for (let i = 0; i < text.length; i++) {
    const c = text.charCodeAt(i)
    if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
      // 非中文算一个字符宽度
      length += 0.5
    } else {
      // 中文算两个个字符宽度
      length += 1
    }
  }
  return fontSize * length
}

/**
 * 对齐 4 字节
 * @param hexArr
 * @return {*[]}
 */
export const alignHex = hexArr => {
  const temp = [...hexArr]
  const left = hexArr.length % 4
  if (left !== 0) {
    temp.push(...chunk('20'.repeat(4 - left), 2))
  }
  return temp
}

/**
 * 字符串转十六进制
 * @param str
 * @return {*[]}
 */
export const stringToHex = str => {
  const words = []
  for (let i = 0; i < str.length; i++) {
    words.push(str.charCodeAt(i).toString(16))
  }
  return words
}

/**
 * 数组分块
 * @param array
 * @param size
 * @return {any[]|*[]}
 */
export const chunk = (array, size = 2) => {
  size = Math.max(size, 0)
  const length = array == null ? 0 : array.length
  if (!length || size < 1) {
    return []
  }
  let index = 0
  let resIndex = 0
  const result = new Array(Math.ceil(length / size))

  while (index < length) {
    result[resIndex++] = array.slice(index, (index += size))
  }
  return result
}

/**
 * 十六进制转字符串
 * @param str
 * @return {string}
 */
export function hexToString(str) {
  const words = []
  const chars = str.split('')
  if (chars.length % 2 !== 0) {
    return ''
  }
  const chunks = chunk(chars).map(value => value.join(''))

  for (let i = 0; i < chunks.length; i++) {
    words.push(String.fromCharCode(parseInt(chunks[i], 16)))
  }
  return words.join('')
}

export class ParamsBuilder {
  constructor() {
    this.params = []
  }

  /**
   * 打开一个 USB 端口
   * 备注：
   * 可以 PTK_GetAllPrinterUSBInfo 查看打印机连接的 USB 端口信息
   * 输入 255 会自动打开一台已连接的打印机的 USB 端口，如果有多台已连接，会优先打开USB识别号靠前的
   * @param port USB 端口号。取值范围 1~255
   */
  openUSBPort(port = 255) {
    this.params.push({ PTK_OpenUSBPort: port })
    return this
  }

  /**
   * 清除打印机中的缓冲内容
   * 备注：
   * 1、当发送新的一张标签内容到打印机前,建议使用此命令先清空打印机缓存里已有的数据内容，防止
   * 打印机有残留数据导致调用 API 函数失败。
   * 2、请不要在存储表单的过程中使用此函数，否则表单会建立失败。
   * @return {ParamsBuilder}
   */
  clearBuffer() {
    this.params.push({ PTK_ClearBuffer: '' })
    return this
  }

  /**
   * 设置当前标签打印方向，掉电不保存
   * 备注：
   * 1、如果通过 FLASH 设置了打印机方向，掉电前以本 API 函数设置的方向为准。
   * 2、PTK_EnableFLASH 函数仅用于保存表单、图片，对此 API 无效。
   * @param direction 方向，取值为 B 或 T。
   *  T -> 从标签左上角开始正常打印, 参考：条码标签打印机的坐标系统
   *  B -> 将从标签右下角开始打印,方向与 T 相反；
   */
  setDirection(direction = 'B') {
    this.params.push({ PTK_SetDirection: direction })
    return this
  }

  /**
   * 设置当前标签打印速度，掉电不保存
   * 备注：
   * 1、不同型号打印机最大打印速度不同（具体请查阅对应用户手册），若设置值大于打印机最大打印
   * 速度，设置无效。
   * 2、 如果通过 FLASH 设置了打印机的打印速度不为 0，如 LCD 屏，则本 API 设置无效。
   * 3、PTK_EnableFLASH 函数仅用于保存表单、图片，对此 API 无效。
   * @param speed 取值范围为0 - 10，或者10 – 100
   */
  setPrintSpeed(speed = '4') {
    this.params.push({ PTK_SetPrintSpeed: speed })
    return this
  }

  /**
   * 设置打印黑度 最大打印黑度 20 树脂碳带一般设置 15 左右
   * @param darkness
   * @return {ParamsBuilder}
   */
  setDarkness(darkness = 20) {
    this.params.push({ PTK_SetDarkness: darkness })
    return this
  }

  /**
   * 设置当前标签高度、定位间隙\黑线\穿孔的高度、定位偏移，掉电不保存
   * 备注：
   * 1、如果不设置，则会按标签校准测出的高度为准。
   * 2、PTK_EnableFLASH 函数仅用于保存表单、图片，对此 API 无效。
   * @param height 标签的高度，以点(dots)为单位，取值范围：0-65535；
   * @param gapH 标签间的定位间隙/黑线/穿孔的高度，以点(dots)为单位，
   *  取值范围: 0-65535；
   * gapH 的取值与标签定位方式相关：
   *  间隙模式(GAP MODE):缺省模式,gapH 设置为间隙的高度，
   * 穿孔定位属于间隙模式的特例，也属于间隙模式；
   *  黑线模式(BLACK LINE MODE): gapH 设置为黑线的高度；
   * 连续纸模式(CONTINUOUS MODE):gapH 设置为 0.这时候,纸张探测器只检测纸张是否用尽。
   * @param gapOffset 标签间隙/黑线/穿孔定位偏移值，以点(dots)为单位
   * @param bFlag gapOffset 是否有效,TRUE-有效；FALSE-无效
   * @return {ParamsBuilder}
   */
  setLabelHeight(height = labelHeight, gapH = gap, gapOffset = 0, bFlag = false) {
    // 必须正确设置标签的宽高--防止内容打偏
    // 宽高可用尺子量取也可以查看标签的规格
    // 一般桌面型打印机的最大打印宽度为110毫米 实际有效打印宽度根据打印机的参数决定
    this.params.push({ PTK_SetLabelHeight: [millimeterToPixel(height), millimeterToPixel(gapH), gapOffset, bFlag] })
    return this
  }

  /**
   * 设置标签宽度
   * @param width
   */
  setLabelWidth(width = labelWidth) {
    this.params.push({ PTK_SetLabelWidth: millimeterToPixel(width) })
    return this
  }

  /**
   * 设置 RFID 标签打印参数
   * 备注:单次打印有效，如果要使用，每次打印前都要调用。不保存到 FLASH
   * @param nReservationParameters 预留参数，默认输入 0
   * @param nReadWriteLocation  RFID 读写位置, 范围：0-999, 默认为 0. 单位 mm.
   * @param ReadWriteArea  预留参数。默认值为 0
   * @param nMaxErrNum 读写错误重试次数 0~9 默认 1
   * @param nErrProcessingMethod  预留参数。默认值为 0
   * @return {ParamsBuilder}
   */
  setRFID(
    nReservationParameters = 0,
    nReadWriteLocation = 0,
    ReadWriteArea = 0,
    nMaxErrNum = 0,
    nErrProcessingMethod = 0
  ) {
    this.params.push({
      PTK_SetRFID: [nReservationParameters, nReadWriteLocation, ReadWriteArea, nMaxErrNum, nErrProcessingMethod]
    })
    return this
  }

  /**
   * 写 RFID 标签数据
   * @param nRWMode RFID 操作方式. 0—(预留：暂无功能)；1—写 RFID；
   * @param nWForm RFID 写入格式. 0—HEX（十六进制）；1—ASCII
   * @param nStartBlock 写入起始块(备注:如针对 EPC 区进行写入操作， 单位:字(2 个字节))
   * @param nWDataNum 写入字节数
   * @param nWArea 写入区域. 0—Reserved（保留区）；1—EPC； 3—USER
   * @param pstr 一个常量字符串。（格式由参数 P2 限制）
   * 备注： 如果采用 HEX 格式写入，nWDataNum 为写入的数据的字节数，不是字符串的字节数。
   * 例：HEX 字符串”313233343536”对应 0x31,0x32,0x33,0x34,0x35,0x36 数据长度 6 个字节
   * 写入数据长度必须以字(2 个字节)为单位，有效数据长度为 2 个字节的整数倍
   * @return {ParamsBuilder}
   */
  rwRFIDLabel(nRWMode = 1, nWForm = 0, nStartBlock = 2, nWDataNum = 6, nWArea = 1, pstr = '313233343536') {
    // PTK_RWRFIDLabel(1, 0, 2, 6, 1, "313233343536");//HEX格式写入
    // PTK_RWRFIDLabel(1, 1, 2, 6, 1, "123456");//ASCII码格式写入，效果同上
    this.params.push({ PTK_RWRFIDLabel: [nRWMode, nWForm, nStartBlock, nWDataNum, nWArea, pstr] })
    return this
  }

  /**
   * 设置高频 RFID
   * @param WForm 读写数据类型 (A: ASCII；H:十六进制方式；默认为ASCII 格式。)
   * @param nProtocol 协议类型 0 : 探测校准时识别到的标签类型；
   *  1: ISO 15693 协议；
   *  2: ISO 14443 协议；
   * 默认为FLASH保存值，通过打印机屏幕或函数PTK_SetAllPrinterInfo设置
   * @param nMaxErrNumd 读写操作失败尝试次数（不包括第一次），最大次数失败后打印“Void”标识，如果有
   * 显示屏，屏幕将提示报错
   * 默认值：1。
   * @return {ParamsBuilder}
   */
  setHFRFID(WForm = 'H', nProtocol = 1, nMaxErrNumd = 3) {
    // 设置高频RFID
    this.params.push({ PTK_SetHFRFID: [WForm, nProtocol, nMaxErrNumd] })
    return this
  }

  /**
   * 写高频标签数据
   * @param nRWMode RFID 操作方式，保留参数，默认为'W'
   * @param nStartBlock 操作起始块(first block number).
   * （注：15693协议设置范围为0至最大支持块，每个块大小为4字节。14443A协议设置范围有两个
   * 限制：一是制造商块（第0扇区第0块）不能写入；二是控制块（前32个扇区：4*n-1；第32扇区开
   * 始：128+16*n-1，n为1/2/3/4......）不能作为起始块，因此该指令写入数据都会跳过控制块，每个
   * 块大小为16字节。NTAG设置范围为4（index 0）至最大支持块，每个块大小为4字节）
   * @param nBlockNum 操作块的个数(Number of blocks)
   * @param pstr 一个长度为 1-100 的字符串。用户可以用”DATA”,Cn,Vn 自由排列组合成一个组合字符串,
   *  “DATA”: 常量字符串，必须用‘”’作为起始和结束符号，如“POSTEK Printer”。
   *  Cn: 序列号数值，此序列号必须已经定义。
   *  Vn: 变量字符串，此变量字符串必须已经定义。
   *  如:”text1”Cn”text2”Vn。
   * @param varible TRUE 表示当前字符串当中包含有变量操作,需加‘\”’将打印常量字符串包含。
   * FALSE 表示当前字符串当中不包含有变量操作，不需加‘\”’。
   * 例如：PTK_RWHFLabel('W', 5, 1, _T("1234"), FALSE);
   * 和 PTK_RWHFLabel('W', 5, 1, _T("\"1234\""), TRUE);效果相同
   * @return {ParamsBuilder}
   */
  rwHFLabel(nRWMode = 'W', nStartBlock = 5, nBlockNum = 1, pstr = '11111111', varible = 0) {
    // 写高频RFID数据
    this.params.push({ PTK_RWHFLabel: [nRWMode, nStartBlock, nBlockNum, pstr, varible] })
    return this
  }

  /**
   * 绘制文本
   * @param options {{x, y , height, text, width, weight, font, rotate, italic, underline, strikeOut, xAxisOffset}}
   */
  drawTrueTypeText(options) {
    const {
      x = 0,
      y = 0,
      height,
      text,
      xAxisOffset = mXAxisOffset,
      width = 0,
      weight = 700,
      font = '宋体',
      rotate = 1,
      italic = 0,
      underline = 0,
      strikeOut = 0
    } = options
    this.params.push({
      PTK_DrawText_TrueType: [
        millimeterToPixel(x + xAxisOffset),
        millimeterToPixel(y),
        millimeterToPixel(height),
        millimeterToPixel(width),
        font,
        rotate,
        weight,
        italic,
        underline,
        strikeOut,
        text
      ]
    })
    return this
  }

  /**
   * 绘制二维码
   * @param options {{ x , y , text, o , r , g , v , s , binname, xAxisOffset }}
   */
  drawQrcode(options) {
    const {
      x = 0,
      y = 0,
      text,
      o = 0,
      r = 4,
      g = 1,
      v = 10,
      s = 8,
      binname = 'ss',
      xAxisOffset = mXAxisOffset
    } = options
    this.params.push({
      PTK_DrawBar2D_QREx: [millimeterToPixel(x + xAxisOffset), millimeterToPixel(y), o, r, g, v, s, binname, text]
    })
    return this
  }

  /**
   * 绘制图形
   * @param options {{ x , y , filePath, pcxname , ratio , width , height , iDire, xAxisOffset  }}
   */
  drawGraphic(options) {
    const {
      x = 0,
      y = 0,
      filePath,
      pcxname = 'logo',
      ratio = 0,
      width = 128,
      height = 128,
      iDire = 0,
      xAxisOffset = mXAxisOffset
    } = options
    this.params.push({
      PTK_AnyGraphicsPrint: [
        millimeterToPixel(x + xAxisOffset),
        millimeterToPixel(y),
        pcxname,
        filePath,
        ratio,
        width,
        height,
        iDire
      ]
    })
    return this
  }

  /**
   * 画矩形
   * @param top
   * @param left
   * @param bottom
   * @param right
   * @param thickness 线条粗细
   * @return {ParamsBuilder}
   */
  drawRectangle({ top, left, bottom, right, thickness = 0.3 }) {
    this.params.push({
      PTK_DrawRectangle: [
        millimeterToPixel(left),
        millimeterToPixel(top),
        millimeterToPixel(thickness),
        millimeterToPixel(right),
        millimeterToPixel(bottom)
      ]
    })
    return this
  }

  /**
   * 画一维条码
   * @param pX x坐标
   * @param pY y坐标
   * @param pdirec 旋转角度
   * @param pCode 类型
   * @param NarrowWidth 一维条码中窄单元的宽度，以点(dots)为单位。
   * @param pHorizontal 一维条码中宽单元的宽度，以点(dots)为单位。
   * @param pVertical 一维条码的高度，以点(dots)为单位
   * @param ptext 供人识别字符
   * @param pstr 一个长度为1-100 的字符串
   * @param Varible 是否含有变量操作
   * @return {ParamsBuilder}
   */
  drawBarcode({ pX, pY, direc, codeType, narrowWidth,horizontal ,vertical, humanReadable, data }) {
    this.params.push({
      PTK_DrawBarcode : [
        millimeterToPixel(pX),
        millimeterToPixel(pY),
        direc,
        codeType,
        millimeterToPixel(narrowWidth),
        millimeterToPixel(horizontal),
        millimeterToPixel(vertical),
        humanReadable,
        data
      ]
    })
    return this
  }

  drawHorizontalLineOr({ startX, startY, length, thickness = 0.3 }) {
    this.params.push({
      PTK_DrawLineOr: [
        millimeterToPixel(startX),
        millimeterToPixel(startY),
        millimeterToPixel(length),
        millimeterToPixel(thickness)
      ]
    })
    return this
  }

  drawVerticalLineOr({ startX, startY, length, thickness = 0.3 }) {
    this.params.push({
      PTK_DrawLineOr: [
        millimeterToPixel(startX),
        millimeterToPixel(startY),
        millimeterToPixel(thickness),
        millimeterToPixel(length)
      ]
    })
    return this
  }

  drawHorizontalLineXOr({ startX, startY, length, thickness = 0.3 }) {
    this.params.push({
      PTK_DrawLineXor: [
        millimeterToPixel(startX),
        millimeterToPixel(startY),
        millimeterToPixel(length),
        millimeterToPixel(thickness)
      ]
    })
    return this
  }

  drawVerticalLineXOr({ startX, startY, length, thickness = 0.3 }) {
    this.params.push({
      PTK_DrawLineXor: [
        millimeterToPixel(startX),
        millimeterToPixel(startY),
        millimeterToPixel(thickness),
        millimeterToPixel(length)
      ]
    })
    return this
  }

  drawLabel30x20({
    unitName = '广海局涉密设备标识广海局涉密设备标识',
    purpose = '办公办公办公办公',
    name = '台式电脑台式电台式电脑台式电',
    isSecret,
    secret = '内部内部内部',
    code = 'XXX-BM-M2-001XXX-BM-M2-001XXX-BM-M2-001',
    mode = '资产名称资资产名称资资产名称资',
    department = '某某部门的某某部门的某某部门的',
    user = '张三丰张三丰张三丰张三丰',
    address = '1#1#1#1#1#1#1#',
    time = '2023-04-142023-04-142023-04-142023-04-142023-04-14'
  } = {}) {
    const xAxisOffset = 0

    const rect = new Rect(0, 0, 27.5, 19.5)
    rect.offset(3.8, 0.2)
    const { left, top, right, bottom, thickness } = rect

    const centerX = rect.centerX() + rect.thickness

    const titleFontSize = 2
    const titleRowHeight = 4
    const titleStartY = rect.top + thickness + (titleRowHeight - titleFontSize) / 2

    const unit = splitString(unitName, 24)[0]
    // 单位名称开始绘制的 X 轴开始位置
    const titleStartX = rect.left + (rect.width() - thickness * 2 - measureText(unit, titleFontSize)) / 2

    const contentHeight = rect.height() - titleRowHeight
    const contentRowHeight = contentHeight / 5 // 包含线高度
    const contentFontSize = 1.5
    const contentTopPadding = (contentRowHeight - contentFontSize) / 2
    const contentLeftPadding = 0.2
    const contentStartX = rect.left + thickness + contentLeftPadding
    const contentStartX2 = centerX + thickness + contentLeftPadding

    let lineBegin = rect.top + titleRowHeight

    // 画一个矩形
    this.drawRectangle({ left, top, right, bottom, thickness })

    // 单位名称
    this.drawTrueTypeText({ xAxisOffset, x: titleStartX, y: titleStartY, height: titleFontSize, text: unit })

    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    if (isSecret) {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `资产名称:${splitString(name, 24)[0]}`
      })
    } else {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `资产名称:${splitString(name, 14)[0]}`
      })
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX + 12.8 * contentFontSize,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `密级:${splitString(secret, 4)[0]}`
      })
    }

    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `资产编码:${splitString(code, 22)[0]}`
    })

    lineBegin += contentRowHeight
    const vStartY = lineBegin
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    if (isSecret) {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `密级:${splitString(secret, 10)[0]}`
      })
    } else {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `用途:${splitString(purpose, 10)[0]}`
      })
    }
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX2,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `型号:${splitString(mode, 10)[0]}`
    })

    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `部门:${splitString(department, 10)[0]}`
    })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX2,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `责任人:${splitString(user, 8)[0]}`
    })

    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `位置:${splitString(address, 10)[0]}`
    })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX2,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `启用:${splitString(time, 10)[0]}`
    })

    this.drawVerticalLineXOr({ startX: centerX, startY: vStartY, length: rect.bottom - vStartY })

    return this
  }

  drawLabel50x50({
    unitName = '广海局涉密设备标识广海局涉密设备标识',
    purpose = '办公办公办公办公办公办公',
    name = '台式电脑台式电台式电脑台式电',
    isSecret = true,
    secret = '内部内部内部',
    code = '资产编码资产编码资产编码资产编码资产编码资产编码资产编码',
    mode = '资产名称资资产名称资资产名称资',
    department = '某某部门的某某部门的某某部门的',
    user = '张三丰张三丰张三丰张三丰',
    address = '位置位置位置位置位置位置',
    time = '启用启用启用启用启用启用启用'
  } = {}) {
    const xAxisOffset = 0

    const rect = new Rect(0, 0, 47, 27.2)
    rect.offset(0.7, 3)
    const { left, top, right, bottom, thickness } = rect

    const centerX = rect.centerX() + rect.thickness

    const titleFontSize = 3
    const titleRowHeight = 5
    const titleStartY = rect.top + thickness + (titleRowHeight - titleFontSize) / 2

    const unit = splitString(unitName, 28)[0]
    // 单位名称开始绘制的 X 轴开始位置
    const titleStartX = rect.left + (rect.width() - thickness * 2 - measureText(unit, titleFontSize)) / 2

    const contentHeight = rect.height() - titleRowHeight
    const contentRowHeight = contentHeight / 5 // 包含线高度
    const contentFontSize = 2.5
    const contentTopPadding = (contentRowHeight - contentFontSize) / 2
    const contentLeftPadding = 0.2
    const contentStartX = rect.left + thickness + contentLeftPadding
    const contentStartX2 = centerX + thickness + contentLeftPadding

    let lineBegin = rect.top + titleRowHeight

    // 画一个矩形
    this.drawRectangle({ left, top, right, bottom, thickness })

    // 单位名称
    this.drawTrueTypeText({ xAxisOffset, x: titleStartX, y: titleStartY, height: titleFontSize, text: unit })

    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    if (isSecret) {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `资产名称:${splitString(name, 26)[0]}`
      })
    } else {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `资产名称:${splitString(name, 16)[0]}`
      })
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX + 13.5 * contentFontSize,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `密级:${splitString(secret, 4)[0]}`
      })
    }

    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `资产编码:${splitString(code, 26)[0]}`
    })

    lineBegin += contentRowHeight
    const vStartY = lineBegin
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    if (isSecret) {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `密级:${splitString(secret, 12)[0]}`
      })
    } else {
      this.drawTrueTypeText({
        xAxisOffset,
        x: contentStartX,
        y: lineBegin + contentTopPadding,
        height: contentFontSize,
        text: `用途:${splitString(purpose, 12)[0]}`
      })
    }
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX2,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `型号:${splitString(mode, 12)[0]}`
    })

    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `部门:${splitString(department, 12)[0]}`
    })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX2,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `责任人:${splitString(user, 10)[0]}`
    })

    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `位置:${splitString(address, 12)[0]}`
    })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX2,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `启用:${splitString(time, 12)[0]}`
    })

    this.drawVerticalLineXOr({ startX: centerX, startY: vStartY, length: rect.bottom - vStartY })

    return this
  }

  drawArmyLabel40x30({
    unitName = '广海局涉密设备标识广海局涉密设备标识',
    purpose = '办公办公办公办公办公办公',
    name = '台式电脑台式电台式电脑台式电',
    isSecret = true,
    secret = '内部内部内部',
    code = '202060023112000',
    type = 'asset', // asset,material
    model = '资产名称资资产名称资资产名称资',
    department = '某某部门的某某部门的某某部门的',
    user = '张三丰张三丰张三丰张三丰',
    address = '位置位置位置位置位置位置',
    value = '3999',
    time = '启用启用启用启用启用启用启用'
  } = {}) {

    const xAxisOffset = 0

    const rect = new Rect(0, 0, 38, 25)
    rect.offset(0.7, 3)
    const { left, top, right, bottom, thickness } = rect

    const titleRowHeight = 5

    const contentHeight = rect.height() - titleRowHeight
    const contentRowHeight = contentHeight / 4.5 // 包含线高度
    const contentFontSize = 2.5
    const contentTopPadding = (contentRowHeight - contentFontSize) / 2
    const contentLeftPadding = 0.2
    const contentStartX = rect.left + thickness + contentLeftPadding

    let lineBegin = rect.top
    // 画一个矩形
    this.drawRectangle({ left, top, right, bottom, thickness })
    const titleObj = {
      asset:{
      "name":"资产名称",
      "code":"资产编码",
      "model":"型号",
      "value":"原值"
    },
      material:{
        "name":"品名",
        "code":"条码",
        "model":"型号",
        "value":"金额"
      }
    }

    const titleJSON = titleObj[type]

    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `${titleJSON.name}:${splitString(name, 16)[0]}`
    })
    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `${titleJSON.code}:${splitString(code, 16)[0]}`
    })
    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `${titleJSON.model}:${splitString(model, 16)[0]}`
    })
    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })
    this.drawTrueTypeText({
      xAxisOffset,
      x: contentStartX,
      y: lineBegin + contentTopPadding,
      height: contentFontSize,
      text: `${titleJSON.value}:${splitString(value, 16)[0]}`
    })
    lineBegin += contentRowHeight
    this.drawHorizontalLineOr({ startX: rect.left, startY: lineBegin, length: rect.width() })

    const { pX, pY, direc, codeType, narrowWidth,horizontal ,vertical, humanReadable, data } =
      {pX: 2, pY: lineBegin + contentTopPadding+0.5, direc: "0", codeType: "1A" , narrowWidth: 0.2, horizontal: 0.2, vertical: 4, humanReadable: 'N' ,data: code}
    // 打印一维条码
    this.drawBarcode({pX, pY, direc, codeType, narrowWidth,horizontal ,vertical, humanReadable, data })
    return this
  }

  /**
   * 命令打印机开始打印标签内容
   * 备注：设置完打印内容，最后都需要调用这个函数开始打印
   * @param number 打印标签的数量，取值范围：1—65535；
   * @param cpnumber 每张标签的复制份数，取值范围：1—65535
   * @return {ParamsBuilder}
   */
  printLabel(number = 1, cpnumber = 1) {
    this.params.push({ PTK_PrintLabel: [number, cpnumber] })
    return this
  }

  /**
   * 打印一张标签，然后返回打印的 RFID 标签数据和打印机的状态
   * 备注:此函数包含 PTK_PrintLabel 的功能，不需要再调用 PTK_PrintLabel 开始打印。
   * 备注:此函数仅支持 V7.61 以上固件
   * @param block 选择数据区域; 0: TID , 1: EPC , 2: TID+EPC， 3：USER
   * @param data 用于存储获取 RFID 标签数据信息
   * @param dataSize data 的空间大小
   * @param printerStatus 打印机当前的状态，返回值格式为 W1XXXX，XXXX 为打印机状态代码
   * @param status printerStatus 的空间大小
   * @return {ParamsBuilder}
   */
  rfidEndPrintLabelFeedBack(block = 0, data = 'TID:', dataSize = 512, printerStatus = 'state:', status = 16) {
    // 执行打印RFID标签 并返回RFID数据  第一个参数：数据区域 ; 0: TID , 1:EPC , 2:TID+EPC 3：USER 第二个参数：“TID:” 自定义数据名称
    // 如果无需返回RFID数据请使用：printparamsJsonArray.push({"PTK_PrintLabel" : "1,1"});即可
    // 若此函数错误返回为获取数据超时关机重启后执行还是一样的结果则是打印机不支持此功能，请直接利用PTK_PrintLabel打印，
    // 若当前打印机不支持此功能又需要此功能，可咨询售后是否能升级打印机，若无法升级请购买新版本的打印机。
    this.params.push({ PTK_RFIDEndPrintLabelFeedBack: [block, data, dataSize, printerStatus, status] })
    return this
  }

  /**
   * 打印一张标签，然后返回打印的 RFID 标签数据
   * 备注:此函数包含 PTK_PrintLabel 的功能，不需要再调用 PTK_PrintLabel 开始打印。
   * @param block 选择数据区域; 0: TID , 1: EPC , 2: TID+EPC， 3：USER
   * @param data 用于存储获取 RFID 标签数据信息。
   * @param dataSize data 的空间大小
   * @return {ParamsBuilder}
   */
  rfidEndPrintLabel(block = 0, data = 'TID:', dataSize = 256) {
    this.params.push({ PTK_RFIDEndPrintLabel: [block, data, dataSize] })
    return this
  }

  /**
   * 读取高频标签 UID
   * @param pFeed 读取数据后是否向前走一张标签;
   *  Y：表示读取信息后进行 feed 一张标签。
   *  N：读取信息后不进行 feed 动作
   * @param data 存储高频标签 UID 的缓冲区
   * @param dataSize data 的空间大小
   * @return {ParamsBuilder}
   */
  readHFLabeUID(pFeed = 'N', data = 'UID:', dataSize = 256) {
    this.params.push({ PTK_ReadHFLabeUID: [pFeed, data, dataSize] })
    return this
  }

  /**
   * 读取高频标签数据
   * @param nStartBlock 电子标签读取起始块(first block number)
   * @param nBlockNum 电子标签读取块的个数(从 nStartBlock 开始，往后 nBlockNum 块)
   * @param pFeed 读取数据后是否向前走一张标签;
   *  Y：表示读取信息后进行 feed 一张标签。
   *  N：读取信息后不进行 feed 动作
   * @param data 存储高频标签数据的缓冲区
   * @param dataSize :data 的空间大小
   * @return {ParamsBuilder}
   */
  readHFLabelData(nStartBlock = 5, nBlockNum = 1, pFeed = 'N', data = 'DATA:', dataSize = 256) {
    this.params.push({ PTK_ReadHFLabelData: [nStartBlock, nBlockNum, pFeed, data, dataSize] })
    return this
  }

  /**
   * 读取 RFID 标签数据
   * @param nDataBlock 选择数据区域; 0: TID , 1: EPC , 2: TID+EPC., 3:USER
   * @param nRFPower 保留。请输入 0。
   * @param bFeed 读取后是否向前走一张标签;
   * TRUE：向前走一张标签有效，
   * FALSE：向前走一张标签无效。
   * @param data 存储 RFID 标签数据的缓冲区
   * @param dataSize buff 的空间大小
   * @return {ParamsBuilder}
   */
  readRFIDLabelData(nDataBlock = 1, nRFPower = 0, bFeed = 0, data = 'TID:', dataSize = 256) {
    this.params.push({ PTK_ReadRFIDLabelData: [nDataBlock, nRFPower, bFeed, data, dataSize] })
    return this
  }

  /**
   * 读取 RFID 标签数据
   * @param  nDataBlock：选择数据读取区域。
   * 0 – TID
   * 1 – EPC
   * 2 – TID+EPC
   * 3 – USER
   * 4 – TID+USER
   * 5 – RESERVED
   * 6 – TID+RESERVED
   * @param nRFPower： 保留。请输入 0。
   * @param bFeed： 读取后是否向前走一张标签;
   * TRUE：向前走一张标签有效，
   * FALSE：向前走一张标签无效。
   * @param accessCode：访问密码。（格式限制为 4 字节 HEX 字符）,默认访问密码为 00000000。
   * @param data：存储 RFID 标签数据的缓冲区
   * @param dataSize：buff 的空间大小
   */
  readRFIDLabelDataEx(nDataBlock = 1, nRFPower = 0, bFeed = 0, accessCode = '00000000', data = 'EPC:', dataSize = 256) {
    this.params.push({ PTK_ReadRFIDLabelDataEx: [nDataBlock, nRFPower, bFeed, accessCode, data, dataSize] })
    return this
  }

  /**
   * 执行打印完关闭打印机USB端口
   */
  closeUSBPort() {
    this.params.push({ PTK_CloseUSBPort: '' })
    return this
  }

  build(reqParam = 1) {
    this.params.forEach(value => {
      for (const key in value) {
        const val = value[key]
        value[key] = Array.isArray(val) ? val.join(',') : val
      }
    })

    return `reqParam=${reqParam}&printparams=${JSON.stringify(this.params)}`
  }
}

/**
 * 绘制标签
 * @param builder {ParamsBuilder}
 * @param data {{ unitName, name, model, code, department, address, user, logo, value, time }}
 * @param data.unitName
 * @param data.name
 * @param data.model
 * @param data.code
 * @param data.department
 * @param data.address
 * @param data.user
 * @param data.logo
 * @param data.value
 * @param data.time
 * @return {*}
 */
export const drawLabel = (builder, data) => {
  const { unitName, name, model, code, department, address, user, logo, value, time, inAccount, category } = data

  const isInAccount = inAccount !== false

  // 行高
  const lineHeight = 4
  // 文字大小 2 ,2.2 ,2.4 ,2.6 ,2.8, 3.0
  const height = 2.8
  // logo
  builder.drawGraphic({ x: 5.7, y: 0, filePath: logo })
  // 单位名称
  builder.drawTrueTypeText({ x: 18.4, y: 2.3, height: 4.6, text: unitName })
  // 二维码
  builder.drawQrcode({
    x: 2.7,
    y: 11.3,
    text: JSON.stringify({ code, 资产名称: name, 规格型号: model, 取得时间: time, 原值: value })
  })
  // 二维码右侧
  let y = 10.3
  let x = 23.5

  //
  const label = `资产名称：${name}`
  let lines = splitString(label, 30)
  for (let i = 0; i < lines.length; i++) {
    if (i > 1) {
      break
    }
    builder.drawTrueTypeText({ x, y, height, text: lines[i] })
    y += lineHeight
  }

  builder.drawTrueTypeText({ x, y, height, text: `规格型号：${model}` })
  y += lineHeight
  builder.drawTrueTypeText({ x, y, height, text: `资产编码：${code}` })
  y += lineHeight

  if (isInAccount) {
    builder.drawTrueTypeText({ x, y, height, text: `原    值：${value}` })
  } else {
    builder.drawTrueTypeText({ x, y, height, text: `分    类：${category}` })
  }

  // 二维码下方
  x = 2
  y = 31.6
  builder.drawTrueTypeText({ x, y, height, text: `使用部门：${department}` })
  y += lineHeight

  if (isInAccount) {
    builder.drawTrueTypeText({ x, y, height, text: `取得时间：${time} 使用人:${user}` })
  } else {
    builder.drawTrueTypeText({ x, y, height, text: `使用人:${user}` })
  }

  //
  y += lineHeight
  const addr = `存放位置：${address}`
  lines = splitString(addr, 44)
  for (let i = 0; i < lines.length; i++) {
    if (i > 1) {
      break
    }

    builder.drawTrueTypeText({ x, y, height, text: lines[i] })
    y += lineHeight
  }

  return builder
}

/**
 * 添加路径参数
 * @param url
 * @param params
 * @return {string}
 */
export const addParams = (url, params) => {
  const parts = []
  for (const paramsKey in params) {
    parts.push(`${paramsKey}=${encodeURIComponent(params[paramsKey])}`)
  }
  const res = `${url}${url.indexOf('?') > -1 ? '&' : '?'}${parts.join('&')}`
  return res.replace('?&', '?').replace('&&', '&')
}

/**
 * 构建参数
 * @param info {{ unitName, name, model, code, department, address, user, logo, value, time }} 数据
 * @param info.unitName 单位名称
 * @param info.name 资产名称
 * @param info.model 规格型号
 * @param info.code 资产编码
 * @param info.department 使用部门
 * @param info.address 存放地址
 * @param info.user 使用人
 * @param info.logo logo 地址
 * @param info.value 原值
 * @param info.time 取得时间
 * @param usbPort usb 端口
 * @return {string}
 */
export const buildParams = (info, usbPort = 255) => {
  const builder = new ParamsBuilder()
  builder
    .openUSBPort(usbPort)
    .clearBuffer()
    .setDirection()
    .setPrintSpeed()
    .setDarkness()
    .setLabelHeight()
    .setLabelWidth()
    .setRFID()
  // 构建标签内容
  drawLabel(builder, info)
  const hex = alignHex(stringToHex(info.code))
  builder.rwRFIDLabel(1, 0, 2, hex.length, 1, hex.join('')).rfidEndPrintLabel().closeUSBPort()

  return builder.build()
}

/**
 * 标签打印
 * @param info
 * @param usbPort
 * @return {string}
 */
export const buildSmallParams = (info, usbPort = 255) => {
  const builder = new ParamsBuilder()
  builder
    .openUSBPort(usbPort)
    .clearBuffer()
    .setDirection()
    .setPrintSpeed()
    .setDarkness()
    // 打印头 Y 轴开始位置
    .setLabelHeight(20.8, 3)
    // 打印头 X 轴开始位置
    .setLabelWidth(35)
    .drawLabel30x20(info)
    .printLabel()
    .closeUSBPort()

  return builder.build()
}

/**
 * 构建大标签参数
 * @param info
 * @param usbPort
 * @return {string}
 */
export const buildBigParams = (info, usbPort = 255) => {
  const builder = new ParamsBuilder()
  builder
    .openUSBPort(usbPort)
    .clearBuffer()
    .setDirection()
    .setPrintSpeed()
    .setDarkness()
    // 打印头 Y 轴开始位置
    .setLabelHeight(31.5, 3)
    // 打印头 X 轴开始位置
    .setLabelWidth(50)
    .drawLabel50x50(info)
    .printLabel()
    .closeUSBPort()

  // builder.params.forEach(value => console.log(JSON.stringify(value)))
  return builder.build()
}
// 打印部队标签
export const buildArmyParams = (info, usbPort = 255) => {
  const builder = new ParamsBuilder()
  builder
    .openUSBPort(usbPort)
    .clearBuffer()
    .setDirection()
    .setPrintSpeed()
    .setDarkness()
    // 打印头 Y 轴开始位置
    .setLabelHeight(31, 3)
    // 打印头 X 轴开始位置
    .setLabelWidth(40)
    .drawArmyLabel40x30(info)
    .printLabel()
    .closeUSBPort()

  // builder.params.forEach(value => console.log(JSON.stringify(value)))
  return builder.build()
}

/**
 * rfid
 * @param info
 * @return {string}
 */
export const buildPrintParams = info => {
  const builder = new ParamsBuilder()
  builder.clearBuffer().setDirection().setPrintSpeed().setDarkness().setLabelHeight().setLabelWidth().setRFID()
  // 构建标签内容
  drawLabel(builder, info)
  const hex = alignHex(stringToHex(info.code))
  builder.rwRFIDLabel(1, 0, 2, hex.length, 1, hex.join('')).rfidEndPrintLabel()

  return builder.build()
}

/**
 * 创建 XHR 请求
 * @param url
 * @param method
 * @return {XMLHttpRequest}
 */
const createXHR = (url, { method }) => {
  let xhr = new XMLHttpRequest()
  if ('withCredentials' in xhr) {
    xhr.open(method, url, true)
  } else if (window.XDomainRequest !== 'undefined') {
    xhr = new window.XDomainRequest()
    xhr.open(method, url, true)
  } else {
    xhr = null
  }
  return xhr
}

/**
 * request
 * @param url
 * @param method
 * @param data
 * @param params
 * @param contentType
 * @return {Promise<Object>}
 */
export const request = (url, { method, data, params, contentType = 'application/x-www-form-urlencoded' }) => {
  return new Promise((resolve, reject) => {
    const xhr = createXHR(addParams(url, params), { method })
    if (xhr) {
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
          const { responseText, status } = xhr
          if (status < 400) {
            if (isString(responseText)) {
              let res = responseText
              try {
                res = JSON.parse(responseText)
              } catch (e) {}
              resolve(res)
            }
          } else {
            reject(Error(`Request Error: ${status}`))
          }
        }
      }
      if (data) {
        xhr.setRequestHeader('Content-Type', contentType)
        xhr.send(data)
      } else {
        xhr.send()
      }
    }
  })
}

/**
 * post 请求
 * @param url
 * @param data
 * @param params
 * @return {Promise<Object>}
 */
const post = (url, { data, params }) => request(url, { method: 'post', data, params })

/**
 * 打印标签
 * @param url
 * @param data
 * @param cb
 * @return {Promise<Object>}
 */
export const printLabel = async (url, data, cb = null) => {
  const res = await post(url, { data: buildParams(data) })
  cb?.(res)
  return res
}

/**
 * 打印小标签
 * @param url
 * @param data
 * @param cb
 * @return {Promise<Object>}
 */
export const printSmallLabel = async (url, data, cb = null) => {
  const res = await post(url, { data: buildSmallParams(data, 255) })
  cb?.(res)
  return res
}

/**
 * 打印大标签
 * @param url
 * @param data
 * @param cb
 * @return {Promise<Object>}
 */
export const printBigLabel = async (url, data, cb = null) => {
  const res = await post(url, { data: buildBigParams(data, 255) })
  cb?.(res)
  return res
}

/**
 * 批量打印标签
 * @param url {string}
 * @param rows {Array<Record>}
 * @param transfer {Function}
 * @param cb {Function}
 * @param complete {Function}
 * @param exitOnFailed
 * @return {Promise<void>}
 */
export const printLabels = async ({ url, rows, transfer = null, cb = null, complete = null, exitOnFailed }) => {
  for (let i = 0; i < rows.length; i++) {
    try {
      await printLabel(url, transfer ? transfer?.(rows[i]) : rows[i], cb)
    } catch (e) {
      console.error(e)
      if (exitOnFailed) {
        break
      }
    }
  }
  complete?.()
}

/**
 * 打印 30*20 涉密标签
 * @param url
 * @param rows
 * @param transfer
 * @param cb
 * @param complete
 * @param exitOnFailed
 * @return {Promise<void>}
 */
export const printSmallSecretLabels = async ({
  url,
  rows,
  transfer = null,
  cb = null,
  complete = null,
  exitOnFailed
}) => {
  for (let i = 0; i < rows.length; i++) {
    try {
      await printSmallLabel(url, transfer ? transfer?.(rows[i]) : rows[i], cb)
    } catch (e) {
      console.error(e)
      if (exitOnFailed) {
        break
      }
    }
  }
  complete?.()
}
/**
 * 打印 50*50 涉密标签
 * @param url
 * @param rows
 * @param transfer
 * @param cb
 * @param complete
 * @param exitOnFailed
 * @return {Promise<void>}
 */
export const printBigSecretLabels = async ({
  url,
  rows,
  transfer = null,
  cb = null,
  complete = null,
  exitOnFailed = false
}) => {
  for (let i = 0; i < rows.length; i++) {
    try {
      await printBigLabel(url, transfer ? transfer?.(rows[i]) : rows[i], cb)
    } catch (e) {
      console.error(e)
      if (exitOnFailed) {
        break
      }
    }
  }
  complete?.()
}
/**
 * 打印 50*30 部队标签
 * @param url
 * @param rows
 * @param transfer
 * @param cb
 * @param complete
 * @param exitOnFailed
 * @return {Promise<void>}
 */
export const printArmyLabels = async ({
  url,
  rows,
  transfer = null,
  cb = null,
  complete = null,
  exitOnFailed = false
}) => {
  for (let i = 0; i < rows.length; i++) {
    try {
      await printArmyLabel(url, transfer ? transfer?.(rows[i]) : rows[i], cb)
    } catch (e) {
      console.error(e)
      if (exitOnFailed) {
        break
      }
    }
  }
  complete?.()
}

// 部队标签
export const printArmyLabel = async (url, data, cb = null) => {
  const res = await post(url, { data: buildArmyParams(data, 255) })
  cb?.(res)
  return res
}

/**
 * 获取打印机列表
 * @param url
 * @param cb
 * @param params
 * @return {Promise<Object>}
 */
export const getPrinterList = (url, cb = null, params = { reqParam: 'printerList' }) => post(url, { params, cb })
